package com.easychat.websocket;

import com.alibaba.fastjson.JSON;
import com.easychat.entity.constants.Constants;
import com.easychat.entity.dto.MessageSendDto;
import com.easychat.entity.dto.WsInitData;
import com.easychat.entity.enums.MessageTypeEnum;
import com.easychat.entity.enums.UserContactApplyStatusEnum;
import com.easychat.entity.enums.UserContactTypeEnum;
import com.easychat.entity.po.*;
import com.easychat.entity.query.*;
import com.easychat.mappers.*;
import com.easychat.redis.RedisComponet;
import com.easychat.utils.JsonUtils;
import com.easychat.utils.StringTools;
import io.netty.channel.Channel;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.util.Attribute;
import io.netty.util.AttributeKey;
import io.netty.util.concurrent.GlobalEventExecutor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.stream.Collectors;


/**
 * @program: easychat-java
 * @description:
 * @author: Mr.Wang
 * @create: 2024-12-07 21:29
 **/
@Component("channelContextUtils")
public class ChannelContextUtils {

    private static final Logger logger = LoggerFactory.getLogger(ChannelContextUtils.class);

    public static final ConcurrentMap<String, Channel> USER_CHANNEL_MAP = new ConcurrentHashMap<>();
    public static final ConcurrentMap<String, ChannelGroup> GROUP_CHANNEL_MAP = new ConcurrentHashMap<>();

    @Resource
    private RedisComponet redisComponet;

    @Resource
    private UserInfoMapper<UserInfo, UserInfoQuery> userInfoMapper;

    @Resource
    private ChatSessionUserMapper<ChatSessionUser, ChatSessionUserQuery> chatSessionUserMapper;

    @Resource
    private UserContactMapper<UserContact, UserContactQuery> userContactMapper;

    @Resource
    private ChatMessageMapper<ChatMessage, ChatMessageQuery> chatMessageMapper;

    @Resource
    private UserContactApplyMapper<UserContactApply, UserContactApplyQuery> userContactApplyMapper;


    public void addContext(String userId, Channel channel) {

        try {
            String channelId = channel.id().toString();
            //AttributeKey是一个泛型类，用于表示一个键，可以用于存储和检索与Channel对象相关的属性
            AttributeKey attributeKey = null;
            if (!AttributeKey.exists(channelId)) {
                attributeKey = AttributeKey.newInstance(channelId);
            } else {
                attributeKey = AttributeKey.valueOf(channelId);
            }
            //channel.attr(attributeKey)：获取Channel的属性对象，attributeKey是一个AttributeKey类型的对象，用于标识属性
            //set(userId)：将userId这个值设置到属性对象中。
            channel.attr(attributeKey).set(userId);

            //从redis中获取联系人id列表
            List<String> userContactList = redisComponet.getUserContactList(userId);
            //遍历出是群组的联系人
            for (String groupId : userContactList) {
                if (groupId.startsWith(UserContactTypeEnum.GROUP.getPrefix())) {
                    //将自己的channel交给此群id对应的groupChannel管理
                    add2Group(groupId, channel);
                }
            }
            //将用户id和channel放入map中
            USER_CHANNEL_MAP.put(userId, channel);
            //保存用户心跳信息
            redisComponet.saveUserHeartBeat(userId);
            //更新用户最后上线时间
            UserInfo updateInfo = new UserInfo();
            updateInfo.setLastLoginTime(new Date());
            userInfoMapper.updateByUserId(updateInfo, userId);

            //给用户发送一些消息

            //获取用户最后离线时间
            UserInfo userInfo = userInfoMapper.selectByUserId(userId);
            Long originalLastOffTime = userInfo.getLastOffTime();
            Long lastOffTime = originalLastOffTime;
            //如果时间太久，只取三天时间
            if (originalLastOffTime != null && System.currentTimeMillis() - Constants.MILLISECOND_3DAYS_AGO > originalLastOffTime) {
                lastOffTime = System.currentTimeMillis() - Constants.MILLISECOND_3DAYS_AGO;
            }

            /**
             * 1.查询用户所有会话信息 避免换设备会话不同步
             */
            ChatSessionUserQuery chatSessionUserQuery = new ChatSessionUserQuery();
            chatSessionUserQuery.setUserId(userId);
            chatSessionUserQuery.setOrderBy("last_receive_time desc");
            List<ChatSessionUser> chatSessionList = chatSessionUserMapper.selectList(chatSessionUserQuery);
            //1 用户会话信息 2 聊天信息 3 好友申请数
            WsInitData wsInitData = new WsInitData();
            //1 用户会话信息
            wsInitData.setChatSessionList(chatSessionList);

            /**
             * 2.查询聊天信息
             *
             */
            //获取群聊id列表
            UserContactQuery contactQuery = new UserContactQuery();
            contactQuery.setUserId(userId);
            contactQuery.setContactType(UserContactTypeEnum.GROUP.getType());
            List<UserContact> groupContactList = userContactMapper.selectList(contactQuery);
            List<String> groupIdList = groupContactList.stream().map(item -> item.getContactId()).collect(Collectors.toList());
            //将自己加入
            groupIdList.add(userId);

            //查询聊天信息
            ChatMessageQuery chatMessageQuery = new ChatMessageQuery();
            chatMessageQuery.setContactIdList(groupIdList);
            chatMessageQuery.setLastReceiveTime(lastOffTime);
            List<ChatMessage> chatMessages = chatMessageMapper.selectList(chatMessageQuery);
            wsInitData.setChatMessageList(chatMessages);


            /**
             * 3.查询用户好友申请 count
             */
            UserContactApplyQuery applyQuery = new UserContactApplyQuery();
            applyQuery.setReceiveUserId(userId);
            applyQuery.setStatus(UserContactApplyStatusEnum.INIT.getStatus());
            applyQuery.setLastApplyTime(originalLastOffTime);
            Integer count = userContactApplyMapper.selectCount(applyQuery);
            wsInitData.setApplyCount(count);


            //发送消息
            MessageSendDto messageSendDto = new MessageSendDto<>();
            messageSendDto.setMessageType(MessageTypeEnum.INIT.getType());
            messageSendDto.setContactId(userId);
            messageSendDto.setExtendData(wsInitData);

            sendMsg(messageSendDto, userId);
        }catch (Exception e) {
            logger.error("初始化链接失败", e);
        }
    }

    /**
     * 用户断线 从存储用户ID和通道映射中移除该用户，更新用户最后断线时间，并清除Redis中的用户心跳信息
     *
     * @param channel
     */
    public void removeContext(Channel channel) {
        Attribute<String> attribute = channel.attr(AttributeKey.valueOf(channel.id().toString()));
        String userId = attribute.get();
        if (!StringTools.isEmpty(userId)) {
            USER_CHANNEL_MAP.remove(userId);
        }
        redisComponet.removeUserHeartBeat(userId);
        //更新用户最后断线时间
        UserInfo userInfo = new UserInfo();
        userInfo.setLastOffTime(System.currentTimeMillis());
        userInfoMapper.updateByUserId(userInfo, userId);

    }

    public void closeContext(String userId) {
        if (StringTools.isEmpty(userId)) {
            return;
        }
        redisComponet.cleanUserTokenByUserId(userId);
        Channel channel = USER_CHANNEL_MAP.get(userId);
        USER_CHANNEL_MAP.remove(userId);
        if (channel != null) {
            channel.close();
        }
    }

    /**
     * 外部发送消息接口
     *
     * @param messageSendDto
     */
    public void sendMessage(MessageSendDto messageSendDto) {
        UserContactTypeEnum typeEnum = UserContactTypeEnum.getByPrefix(messageSendDto.getContactId());
        switch (typeEnum) {
            case USER:
                send2User(messageSendDto);
                break;
            case GROUP:
                sendMsg2Group(messageSendDto);
                break;
        }
    }

    /**
     * 发送消息给用户
     *
     * @param messageSendDto
     */
    private void send2User(MessageSendDto messageSendDto) {
        String contactId = messageSendDto.getContactId();
        sendMsg(messageSendDto, contactId);
        //强制下线
        if (MessageTypeEnum.FORCE_OFF_LINE.getType().equals(messageSendDto.getMessageType())) {
            closeContext(contactId);
        }
    }

    /**
     * 发送消息到群组
     *
     * @param messageSendDto
     */
    private void sendMsg2Group(MessageSendDto messageSendDto) {
        if (messageSendDto.getContactId() == null) {
            return;
        }
        ChannelGroup group = GROUP_CHANNEL_MAP.get(messageSendDto.getContactId());
        if (group == null) {
            return;
        }
        group.writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(messageSendDto)));

        //如果移除群聊
        MessageTypeEnum typeEnum = MessageTypeEnum.getByType(messageSendDto.getMessageType());
        if (typeEnum == MessageTypeEnum.LEAVE_GROUP || typeEnum == MessageTypeEnum.REMOVE_GROUP) {
            String userId = (String) messageSendDto.getExtendData();
            redisComponet.removeUserContact(userId, messageSendDto.getContactId());
            Channel channel = USER_CHANNEL_MAP.get(userId);
            if (channel == null) {
                return;
            }
            group.remove(channel);
        }

        if (typeEnum == MessageTypeEnum.DISSOLUTION_GROUP) {
            GROUP_CHANNEL_MAP.remove(messageSendDto.getContactId());
            group.close();
        }

    }

    public void sendMsg(MessageSendDto messageSendDto, String reciveId) {
        if (reciveId == null) {
            return;
        }
        Channel sendChannel = USER_CHANNEL_MAP.get(reciveId);
        if (sendChannel == null) {
            return;
        }


        //对于消息接收者来说 消息发送人就是联系人 所以这里要将联系人id改为发送人id,加好友打发送给自己的招呼消息需要单独处理
        if (MessageTypeEnum.ADD_FRIEND_SELF.getType().equals(messageSendDto.getMessageType())){
            UserInfo userInfo = (UserInfo) messageSendDto.getExtendData();
            messageSendDto.setMessageType(MessageTypeEnum.ADD_FRIEND.getType());
            messageSendDto.setContactId(userInfo.getUserId());
            messageSendDto.setContactName(userInfo.getNickName());
            messageSendDto.setExtendData(null);

        }else {
            messageSendDto.setContactId(messageSendDto.getSendUserId());
            messageSendDto.setContactName(messageSendDto.getSendUserNickName());
        }
        sendChannel.writeAndFlush(new TextWebSocketFrame(JsonUtils.convertObj2Json(messageSendDto)));
    }


    public void add2Group(String groupId, Channel channel) {
        ChannelGroup group = GROUP_CHANNEL_MAP.get(groupId);
        if (group == null) {
            group = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
            GROUP_CHANNEL_MAP.put(groupId, group);
        }
        if (channel == null) {
            return;
        }
        group.add(channel);
    }

    public void addUser2Group(String userId, String groupId) {
        Channel channel = USER_CHANNEL_MAP.get(userId);
        add2Group(groupId, channel);
    }
}
